#include "audio_decode.h"
extern "C" {
#include <ffmpeg/libavformat/avformat.h>
#include <ffmpeg/libavutil/adler32.h>
#include <ffmpeg/libavutil/imgutils.h>
#include <ffmpeg/libavcodec/avcodec.h>
}

class AudioDecodeImpl
        : public AudioDecode {
public:
    AudioDecodeImpl();
    ~AudioDecodeImpl();
public:
    virtual bool Open(const std::string &file) override;
    virtual void Close() override;
    virtual void Play(bool loop) override;
    virtual void Stop() override;
    virtual void Pause() override;
    virtual void Continue() override;
    virtual void Seek(double pos) override;
    virtual double Duration() override;
    virtual void SetCallback(funAudioDecode fun);
protected:
    AVFormatContext *fmt_ctx_;
    AVCodecContext *ctx_;
    funAudioDecode fun_callback_;
    int audio_stream_;
};
//----------------------------//
std::shared_ptr<AudioDecode> AudioDecode::NewInterface() {
    return std::shared_ptr<AudioDecode>(new AudioDecodeImpl);
}

//----------------------------//
AudioDecodeImpl::AudioDecodeImpl()
        : fmt_ctx_(nullptr)
        , ctx_(nullptr)
        , fun_callback_(nullptr)
        , audio_stream_(-1) {
    av_register_all();
}

AudioDecodeImpl::~AudioDecodeImpl() {
    
}

bool AudioDecodeImpl::Open(const std::string &file) {
    int result = avformat_open_input(&fmt_ctx_, file.c_str(), nullptr, nullptr);
    if (result < 0) {
        av_log(nullptr, AV_LOG_ERROR, "Can't open file\n");
        return false;
    }

    result = avformat_find_stream_info(fmt_ctx_, nullptr);
    if (result < 0) {
        av_log(nullptr, AV_LOG_ERROR, "Can't get stream info\n");
        return false;
    }

    audio_stream_ = av_find_best_stream(fmt_ctx_, AVMEDIA_TYPE_AUDIO, -1, -1, nullptr, 0);
    if (audio_stream_ < 0) {
      av_log(NULL, AV_LOG_ERROR, "Can't find video stream in input file\n");
      return false;
    }

    AVCodecParameters *origin_par = fmt_ctx_->streams[audio_stream_]->codecpar;

    AVCodec *codec = avcodec_find_decoder(origin_par->codec_id);
    if (!codec) {
        av_log(NULL, AV_LOG_ERROR, "Can't find decoder\n");
        return false;
    }

    ctx_ = avcodec_alloc_context3(codec);
    if (!ctx_) {
        av_log(NULL, AV_LOG_ERROR, "Can't allocate decoder context\n");
        return false;
    }

    result = avcodec_parameters_to_context(ctx_, origin_par);
    if (result) {
        av_log(NULL, AV_LOG_ERROR, "Can't copy decoder context\n");
        return false;
    }

    result = avcodec_open2(ctx_ctx, codec, NULL);
    if (result < 0) {
        av_log(ctx_, AV_LOG_ERROR, "Can't open decoder\n");
        return false;
    }

    return true;
}

void AudioDecodeImpl::Close() {
    avcodec_close(ctx_);
    avformat_close_input(&fmt_ctx_);
    avcodec_free_context(&ctx_);
    audio_stream_ = -1;
    ctx_ = nullptr;
    fmt_ctx_ = nullptr;
    fun_callback_ = nullptr;
}

void AudioDecodeImpl::Play(bool loop) {
    AVPacket pkt;
    av_init_packet(&pkt);
    AVFrame *fr = av_frame_alloc();
    if (!fr) {
        av_log(nullptr, AV_LOG_ERROR, "Can't allocate frame\n");
        return AVERROR(ENOMEM);
    }

    int end_of_stream = 0;
    int got_frame = 0;
    int result = -1;
    int i = 0;
    int frame_bytes = 0;

    do {
        if (!end_of_stream)
            if (av_read_frame(fmt_ctx_, &pkt) < 0)
                end_of_stream = 1;
        if (end_of_stream) {
            pkt.data = nullptr;
            pkt.size = 0;
        }
        if (pkt.stream_index == audio_stream_ || end_of_stream) {
            got_frame = 0;
            if (pkt.pts == AV_NOPTS_VALUE)
                pkt.pts = pkt.dts = i;
            result = avcodec_decode_audio4(ctx_, fr, &got_frame, &pkt);
            if (result < 0) {
                av_log(nullptr, AV_LOG_ERROR, "Error decoding frame\n");
                break;
            }
            if (got_frame) {
                frame_bytes = fr->nb_samples * av_frame_get_channels(fr) * sizeof(uint16_t);
                if (frame_bytes > fr->linesize[0]) {
                    av_log(nullptr, AV_LOG_ERROR, "Incorrect value of output frame linesize\n");
                    break;
                }
                fun_callback_(fr->data[0], frame_bytes);
            }
            av_packet_unref(&pkt);
            av_init_packet(&pkt);
        }
        i++;
    } while (!end_of_stream || got_frame);

    av_frame_free(&fr);
    av_packet_unref(&pkt);
}

void AudioDecodeImpl::Stop() {

}

void AudioDecodeImpl::Pause() {

}

void AudioDecodeImpl::Continue() {

}

void AudioDecodeImpl::Seek(double pos) {

}

double AudioDecodeImpl::Duration() {
    return 0.0f;
}

void AudioDecodeImpl::SetCallback(funAudioDecode fun) {
    fun_callback_ = fun;
}

//----------------------------//